/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.paboo.chart.qrcode;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.zxing.WriterException;
import org.paboo.codec.Base64;
import org.paboo.util.*;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 * @author Leonard
 */
@RestController
@ExposesResourceFor(QREntity.class)
public class QrController {

    private static final long serialVersionUID = -3904063115792721985L;
    private LoggerFactoryUtils log = LoggerFactoryUtils.getInstance().load(QrController.class);

    @RequestMapping("/qr")
    public ResponseEntity<byte[]> handleControllerExecution(HttpMethod method,
                                                            @RequestParam(value = "data", required = false, defaultValue = ParameterObject.DEFAULT_CONTECT) String ctx,
                                                            @RequestParam(value = "encoding", required = false, defaultValue = ParameterObject.DEFAULT_ENCODING) String encoding,
                                                            @RequestParam(value = "eclevel", required = false, defaultValue = "l") String ecLevel,
                                                            @RequestParam(value = "size", required = false, defaultValue = "200x200") String size,
                                                            @RequestParam(value = "margin", required = false, defaultValue = "1") int margin,
                                                            @RequestParam(value = "bgcolor", required = false, defaultValue = "DEFAULT") String bgcolor,
                                                            @RequestParam(value = "fgcolor", required = false, defaultValue = "DEFAULT") String fgcolor,
                                                            @RequestParam(value = "format", required = false, defaultValue = "png") String format,
                                                            @RequestBody(required = false) String requestStr) throws IOException {

        QREntity e = new QREntity();
        MediaType mt = MediaType.TEXT_PLAIN;

        if (!ParameterObject.hasText(requestStr)) {
            requestStr = "{}";
        }

        if (method == HttpMethod.GET) {

            if (!(ZxingValidation.checkEncoding(encoding) && ZxingValidation.checkEcLevel(ecLevel)
                    && ZxingValidation.checkSize(size)
                    && (format.contains("|") ? ZxingValidation.checkFormat(format.split("\\|")[0].toLowerCase()) : ZxingValidation.checkFormat(format.toLowerCase()))
            )) {
                String errorMsg = "Data parameter ERROR!";
                log.warn(errorMsg);

                return ResponseUtils.sendErrorOverJSON(HttpStatus.BAD_REQUEST, errorMsg);
            }

            e.setData(URLDecoder.decode(ctx, encoding));
            e.setEncoding(encoding);
            e.setEcLevel(ZxingUtil.parseErrorCorrectLevel(ecLevel));
            e.setMargin(margin);

            String[] sizea = size.split("x");
            e.setWidth(Integer.parseInt(sizea[0]));
            e.setHeight(Integer.parseInt(sizea[1]));

            e.setBackgroundColor((ZxingValidation.isHex(bgcolor) ? Integer.valueOf(bgcolor.substring(2), 16) : ZxingImage.WHITE));
            e.setForegroundColor((ZxingValidation.isHex(fgcolor) ? Integer.valueOf(fgcolor.substring(2), 16) : ZxingImage.BLACK));

            e.setNeedBase64(false);
            if (format.contains("|")) {
                String[] f = format.split("\\|");
                format = f[0];
                e.setNeedBase64(f[1].equalsIgnoreCase("base64"));
            }
            e.setFormat(format.toLowerCase());
            log.info(new Gson().toJson(e));
        } else if (method == HttpMethod.POST) {
            //BufferedReader br = new BufferedReader(new InputStreamReader(requestIS, encoding));
            Gson gson = new Gson();
            try {
                e = gson.fromJson(requestStr, QREntity.class);
            } catch (JsonIOException | JsonSyntaxException ex) {
                String errorMsg = "Data parameter ERROR!";
                log.error(errorMsg);
                return ResponseUtils.sendErrorOverJSON(HttpStatus.BAD_REQUEST, errorMsg);
            }
            if (e == null) {
                e = new QREntity();
            }
            if (!ParameterObject.hasText(e.getData())) {
                e.setData(ParameterObject.DEFAULT_CONTECT);
            }
            if (!ParameterObject.hasText(e.getEncoding()) || !ZxingValidation.checkEncoding(e.getEncoding().toLowerCase())) {
                e.setEncoding(StandardCharsets.ISO_8859_1.name());
            }
            if (e.getEcLevel() == null) {
                e.setEcLevel(ZxingUtil.parseErrorCorrectLevel("l"));
            }
            if (!ParameterObject.hasInteger(e.getMargin())) {
                e.setMargin(1);
            }
            if (!ParameterObject.hasInteger(e.getWidth()) || !ParameterObject.hasInteger(e.getHeight())) {
                e.setWidth(200);
                e.setHeight(200);
            }
//            if (e.getWidth() != e.getHeight()) {
//                e.setWidth(Math.max(e.getWidth(), e.getHeight()));
//                e.setHeight(Math.max(e.getWidth(), e.getHeight()));
//            }
            if (!ParameterObject.hasInteger(e.getBackgroundColor())) {
                e.setBackgroundColor(ZxingImage.WHITE);
            }
            if (!ParameterObject.hasInteger(e.getForegroundColor())) {
                e.setForegroundColor(ZxingImage.BLACK);
            }
            if (!ParameterObject.hasText(e.getFormat()) || !ZxingValidation.checkFormat(e.getFormat().toLowerCase())) {
                e.setFormat("png");
            }
            if (!ParameterObject.hasBoolean(e.isNeedBase64())) {
                e.setNeedBase64(false);
            }
            log.info(gson.toJson(e));
        } else {
            return ResponseUtils.sendErrorOverJSON(HttpStatus.NOT_IMPLEMENTED, "Request error!!!");
        }

        if ("text".equalsIgnoreCase(e.getFormat())) {
            String outMsg;
            try {
                outMsg = ParameterObject.convertByteArrays(ZxingUtil.encode(e).getArray());
            } catch (IllegalArgumentException | WriterException ex) {
                outMsg = "Create Fail!!!";
                if (log.isDebugEnabled()) {
                    log.error(outMsg, ex);
                } else {
                    log.error(outMsg);
                }
                return ResponseUtils.sendErrorOverJSON(HttpStatus.INTERNAL_SERVER_ERROR, outMsg);
            }
            return ResponseUtils.sendOkContent(mt, outMsg.getBytes());
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        if ("svg".equalsIgnoreCase(e.getFormat())) {
            try {
                mt = MediaType.parseMediaType("image/svg+xml");

                ZxingImage.toSVGDocument(ZxingUtil.encode(e),
                        new OutputStreamWriter(new BufferedOutputStream(out), StandardCharsets.ISO_8859_1.name()),
                        e.getWidth(), e.getHeight(), e.getMargin(), e.getForegroundColor(), e.getBackgroundColor());

                return ResponseUtils.sendOkContent(mt, out.toByteArray());
            } catch (Exception ex) {
                String errorMsg = "Create Fail!!!";
                if (log.isDebugEnabled()) {
                    log.error(errorMsg, ex);
                } else {
                    log.error(errorMsg);
                }
                return ResponseUtils.sendErrorOverJSON(HttpStatus.INTERNAL_SERVER_ERROR, errorMsg);
            }
        }
        BufferedImage data;
        try {
            data = ZxingUtil.encodeWithImg(e);
        } catch (IllegalArgumentException | WriterException ex) {
            String errorMsg = "Create Fail!!!";
            if (log.isDebugEnabled()) {
                log.error(errorMsg, ex);
            } else {
                log.error(errorMsg);
            }
            return ResponseUtils.sendErrorOverJSON(HttpStatus.INTERNAL_SERVER_ERROR, errorMsg);
        }

        if (!ImageIO.write(data, e.getFormat().toLowerCase(), out)) {
            String errorMsg = "Could not write an image of format " + e.getFormat().toLowerCase();
            log.error(errorMsg);
            return ResponseUtils.sendErrorOverJSON(HttpStatus.INTERNAL_SERVER_ERROR, errorMsg);
        }
        if (e.isNeedBase64()) {
            String base64 = Base64.encodeToString(out.toByteArray());

            return ResponseUtils.sendOkContent(mt, base64.getBytes());
        } else {
            if (e.getFormat().equalsIgnoreCase("png")) {
                mt = MediaType.IMAGE_PNG;
            } else if (e.getFormat().equalsIgnoreCase("gif")) {
                mt = MediaType.IMAGE_GIF;
            } else if (e.getFormat().equalsIgnoreCase("jpg") || e.getFormat().equalsIgnoreCase("jpeg")) {
                mt = MediaType.IMAGE_JPEG;
            } else {
                return ResponseUtils.sendErrorOverJSON(HttpStatus.NOT_IMPLEMENTED, "Not implement image");
            }

            return ResponseUtils.sendOkContent(mt, out.toByteArray());
        }


    }
}
